
#include <Windows.h> // Include the Windows API (Win32 Application Programming Interface)
#include <tchar.h> // Include UNICODE support
#include <sstream> // Include ostringstream support
using std::wostringstream;


#include <commdlg.h> // Include the Common Dialogs.
#include <process.h> // Include Multi-Threading functions.
#include "Game.h" // Include the Game Class.
#include "../resource.h"

#include <vld.h>

#ifndef DEBUG_GAME
#define DEBUG_GAME 1
#endif

int				WINDOW_WIDTH		= 800;							//	Window Width.5
int				WINDOW_HEIGHT		= 600;							//	Window Height.
const BOOL		IS_WINDOWED			= FALSE;							//	Window Status: Windowed / Fullscreen

Game* pGame = nullptr;


// Foward Declarations:
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void OnPaint(HWND hWnd);
void OnLButtonDown(HWND hWnd, WPARAM wParam, LPARAM lParam);
void OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam);
void GetDesktopResolution(int& width, int& height);


// Entry point function for the game:
unsigned int WINAPI ProjectMain(void* lpvArgList);


// The Entry Point function
int WINAPI _tWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int iCmdShow )
{
	// Initialize the COM Library.
	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
	

	// Step 1: Initialize the Window Class.
	WNDCLASSEX wcex;
	wcex.cbSize = sizeof(wcex);
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON1));
	wcex.hIconSm = LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON1));
	wcex.hInstance = hInstance;
	wcex.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = WindowProc; // Function to send Windows Message to for processing.
	wcex.lpszMenuName = nullptr;//MAKEINTRESOURCE(IDR_MENU1);
	wcex.lpszClassName = _T("S.M.A.R.T."); // Name to identify this Window Class.
	RegisterClassEx(&wcex);

	//GetDesktopResolution(WINDOW_WIDTH, WINDOW_HEIGHT);

	// Step 2: Create the Main Window.

#if DEBUG_GAME == 0
	HWND hWnd = CreateWindowEx(WS_EX_APPWINDOW, wcex.lpszClassName, _T("S.M.A.R.T."), WS_VISIBLE | WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, 
		WINDOW_WIDTH, WINDOW_HEIGHT, HWND_DESKTOP, NULL, hInstance, NULL);


	GetDesktopResolution(WINDOW_WIDTH, WINDOW_HEIGHT);

	SetWindowLongPtr(hWnd, GWL_STYLE, WS_SYSMENU | WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE);
	SetWindowPos(hWnd, HWND_TOP, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, SWP_SHOWWINDOW);
#else
	HWND hWnd = CreateWindowEx(WS_EX_APPWINDOW, wcex.lpszClassName, _T("S.M.A.R.T."), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 
		WINDOW_WIDTH, WINDOW_HEIGHT, HWND_DESKTOP, NULL, hInstance, NULL);
#endif
	
	if (! hWnd)
	{
		//DWORD err = GetLastError();
		MessageBox(HWND_DESKTOP, _T("ERROR: Failed to create main window!"),
			_T("Program Name"), MB_OK | MB_ICONERROR);
		return -1;
	}


	// Step 2.5: Create the Game thread:
	Game::GetInstance()->hGameThread = (HANDLE)_beginthreadex(NULL, 0,
											  ProjectMain, (void*)hWnd, 0, NULL);
	
	// Set the priority of the game thread to be the same as the main process.	
	SetThreadPriority(Game::GetInstance()->hGameThread, GetThreadPriority(GetCurrentThread()));

#ifdef _DEBUG
	Sleep(1);  // Sleep after thread creation for debugging to work properly.
#endif


	// Step 3: Show the Main Window.
	ShowWindow(hWnd, iCmdShow);
	UpdateWindow(hWnd);


	// Step 4: Enter the Main Message Loop.
	MSG msg = {};
	while (GetMessage(&msg, NULL, 0, 0)) // Getting WM_QUIT makes GetMessage return FALSE.
	{
		TranslateMessage(&msg); // Generate WM_CHAR messages (case sensitive input).
		DispatchMessage(&msg); // Invoke the WindowProc for message processing.
	}


	// Destroy the Game Thread:
	CloseHandle(pGame->hGameThread);

	pGame->End();
	pGame->DeleteInstance();

	// Uninitialize the COM Library.
	CoUninitialize();


	return msg.wParam;
}


// Entry point function for the game:
unsigned int WINAPI ProjectMain(void* lpvArgList)
{
	// Initialize the COM Library.
	CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);

	// Increase the accuracy/precision of the Windows timer:
	TIMECAPS tc;
	timeGetDevCaps(&tc, sizeof(TIMECAPS));
	timeBeginPeriod(tc.wPeriodMin);


	// Get the HWND value out of the ArgList parameter.
	HWND hWnd = (HWND)lpvArgList;
	pGame = Game::GetInstance();

	pGame->Begin(hWnd, (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), WINDOW_WIDTH, WINDOW_HEIGHT, IS_WINDOWED);

	while (pGame->bRunning)
	{
		pGame->Main();

		// 1 Frame == 1 iteration of Input, Simulate, & Render
	}


	PostMessage(hWnd, WM_CLOSE, 0, 0);

	// Restore the accuracy/precision of the Windows timer:
	timeEndPeriod(tc.wPeriodMin);

	// Uninitialize the COM Library.
	CoUninitialize();

	return 0;
}

// Step 5: Write the Window Procedure
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	// Handle Window Creation:
	case WM_CREATE:
		break;

	// Handle Close Button [X] Pressed:
	case WM_CLOSE:
		pGame->bRunning = FALSE; // Begin ProjectMain shutdown.

		// Wait until Game Thread terminates:
		WaitForSingleObject(pGame->hGameThread, INFINITE);

		DestroyWindow(hWnd); // Begin WinMain shutdown.
		break;

	// Handle Window Destruction:
	case WM_DESTROY:
		PostQuitMessage(0); // Put the WM_QUIT message in the message queue.
		break;



	// Handle Window Rendering:
	case WM_PAINT: //(output)
		{
			PAINTSTRUCT	ps;
			HDC			hdc;

			//	Start painting
			hdc = BeginPaint( hWnd,&ps );

			//	End painting
			EndPaint( hWnd,&ps );
			break;
		}


	//	Gain / lose focus
	case WM_ACTIVATE:
		{
			if(pGame != nullptr)
			{
				if( LOWORD( wParam ) != WA_INACTIVE )	//	gaining focus
				{
					pGame->UnpauseGame();
				}
				else									//	losing focus
				{
					// Pause game here
					pGame->PauseGame();
				}
			}
		}
		break;

	case WM_STYLECHANGED:
		{
			return 0;
		}
		break;

	// Handle Menu Items:
	case WM_COMMAND:
		OnCommand(hWnd, wParam, lParam);
		break;
	default:
		// Allow Windows to perform default processing.
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}
	return 0;
}


void OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
	int menuid = LOWORD(wParam);
	//switch(menuid)
	//{
	////case IDM_FILE_EXIT:
	////	PostMessage(hWnd, WM_CLOSE, 0, 0);
	////	break;
	////default:
	////	break;
	//}
}

// Gets the resolution of the desktop.
void GetDesktopResolution(int& width, int& height)
{
	RECT desktop;

	const HWND hDesktop = GetDesktopWindow();

	GetWindowRect(hDesktop, &desktop);

	width = desktop.right;
	height = desktop.bottom;
}